﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ThunderFish
{
    class Sprite
    {
        private SpriteSheet spritesheet;

        /// <summary>
        /// The screen position of this sprite.
        /// </summary>
        public Vector2 Position { get; set; }
        /// <summary>
        /// The offset of this sprite's graphic from its actual position.
        /// </summary>
        public Vector2 Offset { get; set; }
        /// <summary>
        /// The angle of this sprite (in radians)
        /// </summary>
        public float Angle { get; set; }

        /// <summary>
        /// Set this sprite's offset such that it is centered.
        /// </summary>
        public void CenterOffset()
        {
            Offset = new Vector2(Width, Height) / 2;
        }

        /// <summary>
        /// The length of one frame in the sprite's animation.
        /// </summary>
        public TimeSpan FrameLength { get; set; }

        /// <summary>
        /// Get the current frame this sprite is displaying.
        /// </summary>
        /// <returns>The integer frame index</returns>
        public int GetFrame() { return currentFrame; }

        private TimeSpan nextFrame;
        private int currentFrame;

        public int CurrentFrame { get { return currentFrame; } }

        private int currentStartFrame;
        private int currentEndFrame;
        /// <summary>
        /// Is this sprite looping?
        /// </summary>
        public bool IsLooping { get; protected set; }
        /// <summary>
        /// Is this sprite playing (can be true if sprite is paused)?
        /// </summary>
        public bool IsPlaying { get; protected set; }
        /// <summary>
        /// Is this sprite paused?
        /// </summary>
        public bool IsPaused { get; protected set; }

        /// <summary>
        /// The width of this sprite.
        /// </summary>
        public int Width { get { return spritesheet.FrameWidth; } }
        /// <summary>
        /// The height of this sprite.
        /// </summary>
        public int Height { get { return spritesheet.FrameHeight; } }

        /// <summary>
        /// Get the rectangle containing this sprite.
        /// </summary>
        public Rectangle CollisionBox
        {
            get
            {
                return new Rectangle(
                    (int)(Position.X - Offset.X),
                    (int)(Position.Y - Offset.Y),
                    Width,
                    Height);
            }
        }

        /// <summary>
        /// Get the current frame of this sprite.
        /// </summary>
        /// <returns></returns>
        public Texture2D GetCurrentFrame()
        {
            return spritesheet.GetFrame(currentFrame);
        }

        /// <summary>
        /// Create a new sprite
        /// </summary>
        /// <param name="spritesheet">The spritesheet to draw frames from</param>
        public Sprite(SpriteSheet spritesheet)
        {
            this.spritesheet = spritesheet;

            currentStartFrame = 0;
            currentEndFrame = spritesheet.FrameCount;
        }

        /// <summary>
        /// Update this sprite's animation
        /// </summary>
        /// <param name="elapsedTime">Amount of time elapsed since last update</param>
        public void Update(TimeSpan elapsedTime)
        {
            if (!IsPaused && IsPlaying)
            {
                nextFrame -= elapsedTime;
                if (nextFrame <= TimeSpan.Zero)
                {
                    currentFrame++;
                    if (currentFrame > currentEndFrame)
                    {
                        if (IsLooping) currentFrame = currentStartFrame;
                        else
                        {
                            IsPlaying = false;
                            currentFrame--;
                        }
                    }
                    nextFrame = FrameLength;
                }
            }
        }

        /// <summary>
        /// Play this sprite's animation
        /// </summary>
        public void PlayAnimation()
        {
            PlayAnimation(0, spritesheet.FrameCount - 1, false);
        }

        /// <summary>
        /// Loop this sprite's animation
        /// </summary>
        public void LoopAnimation()
        {
            PlayAnimation(0, spritesheet.FrameCount - 1, true);
        }

        /// <summary>
        /// Loop the specified range of frames from the sprite's animation
        /// </summary>
        /// <param name="startFrame"></param>
        /// <param name="endFrame"></param>
        public void LoopAnimation(int startFrame, int endFrame)
        {
            PlayAnimation(startFrame, endFrame, true);
        }

        /// <summary>
        /// Play the specified range of frames from the sprite's animation
        /// </summary>
        /// <param name="startFrame"></param>
        /// <param name="endFrame"></param>
        /// <param name="loop">Should the animation loop?</param>
        public void PlayAnimation(int startFrame, int endFrame, bool loop)
        {
            nextFrame = FrameLength;
            currentFrame = startFrame;
            IsPaused = false;
            IsLooping = loop;
            currentStartFrame = startFrame;
            currentEndFrame = endFrame;
            IsPlaying = true;
        }

        /// <summary>
        /// Pause this sprite's animation
        /// </summary>
        public void Pause()
        {
            IsPaused = true;
        }

        /// <summary>
        /// Resume this sprite's animation if it is paused
        /// </summary>
        public void Resume()
        {
            IsPaused = false;
        }

        /// <summary>
        /// Stop this sprite's animation
        /// </summary>
        public void StopAnimation()
        {
            IsPaused = false;
            IsPlaying = false;
            IsLooping = false;
        }

        /// <summary>
        /// Set the frame this sprite is showing
        /// </summary>
        /// <param name="frame"></param>
        public void SetFrame(int frame)
        {
            currentFrame = frame;
        }

        /// <summary>
        /// Draw this sprite to the specified SpriteBatch
        /// </summary>
        /// <param name="batch"></param>
        public void Draw(SpriteBatch batch)
        {
            batch.Draw(
                spritesheet.GetGraphic(),
                Position,
                spritesheet.GetRectangle(currentFrame),
                Color.White,
                Angle,
                Offset,
                1.0f,
                SpriteEffects.None,
                1.0f);
        }

        public void Draw(SpriteBatch batch, Rectangle destinationRectangle, Color color)
        {
            batch.Draw(
                spritesheet.GetFrame(currentFrame),
                destinationRectangle,
                color);
        }

        public void Draw(SpriteBatch batch, Vector2 position, Color color)
        {
            batch.Draw(
                spritesheet.GetFrame(currentFrame),
                position,
                color);
        }

        public void Draw(SpriteBatch batch, Rectangle destinationRectangle,
            Rectangle? sourceRectangle, Color color)
        {
            batch.Draw(
                spritesheet.GetFrame(currentFrame),
                destinationRectangle,
                sourceRectangle,
                color);
        }

        public void Draw(SpriteBatch batch, Vector2 position,
            Rectangle? sourceRectangle, Color color)
        {
            batch.Draw(
                spritesheet.GetFrame(currentFrame),
                position,
                sourceRectangle,
                color);
        }

        public void Draw(SpriteBatch batch,
            Rectangle destinationRectangle,
            Rectangle? sourceRectangle,
            Color color,
            float rotation,
            Vector2 origin,
            SpriteEffects effects,
            float layerDepth)
        {
            batch.Draw(
                spritesheet.GetFrame(currentFrame),
                destinationRectangle,
                sourceRectangle,
                color,
                rotation,
                origin,
                effects,
                layerDepth);
        }

        public void Draw(SpriteBatch batch,
            Vector2 position,
            Rectangle? sourceRectangle,
            Color color,
            float rotation,
            Vector2 origin,
            float scale,
            SpriteEffects effects,
            float layerDepth)
        {
            batch.Draw(
                spritesheet.GetFrame(currentFrame),
                position,
                sourceRectangle,
                color,
                rotation,
                origin,
                scale,
                effects,
                layerDepth);
        }

        public void Draw(SpriteBatch batch,
            Vector2 position,
            Rectangle? sourceRectangle,
            Color color,
            float rotation,
            Vector2 origin,
            Vector2 scale,
            SpriteEffects effects,
            float layerDepth)
        {
            batch.Draw(
                spritesheet.GetFrame(currentFrame),
                position,
                sourceRectangle,
                color,
                rotation,
                origin,
                scale,
                effects,
                layerDepth);
        }

        /// <summary>
        /// Does this sprite collide with the specified sprite using box collision?
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool RectangleHit(Sprite other)
        {
            return other.CollisionBox.Intersects(CollisionBox);
        }

        /// <summary>
        /// Does this sprite collide with any of the specified sprites using box collision?
        /// </summary>
        /// <param name="others"></param>
        /// <returns>The other sprite collided with</returns>
        public Sprite RectangleHitAny(ICollection<Sprite> others)
        {
            foreach (Sprite s in others)
                if (RectangleHit(s)) return s;
            return null;
        }

        /// <summary>
        /// Does this sprite collide with the specified sprite using circle collision?
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool CircleHit(Sprite other)
        {
            return Vector2.Distance(Position, other.Position) <=
                Math.Min(Width, Height) + Math.Min(other.Width, other.Height);
        }

        /// <summary>
        /// Does this sprite collide with any of the specified sprites using circle collision?
        /// </summary>
        /// <param name="others"></param>
        /// <returns>The other sprite collided with</returns>
        public Sprite CircleHitAny(ICollection<Sprite> others)
        {
            foreach (Sprite s in others)
                if (CircleHit(s)) return s;
            return null;
        }

        /// <summary>
        /// Does this sprite collide with the specified sprite using per-pixel detection?
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool PerPixelHit(Sprite other)
        {
            if (!RectangleHit(other)) return false;

            Texture2D thisframe = GetCurrentFrame();
            Color[] thisdata = new Color[thisframe.Width * thisframe.Height];
            thisframe.GetData<Color>(thisdata);

            Texture2D otherframe = other.GetCurrentFrame();
            Color[] otherdata = new Color[otherframe.Width * otherframe.Height];
            otherframe.GetData<Color>(thisdata);

            Vector2 otheroffset = (other.Position - other.Offset) - (Position - Offset);

            Rectangle check = Rectangle.Intersect(other.CollisionBox, CollisionBox);

            for (int x = check.Left; x < check.Right; x++)
            {
                for (int y = check.Top; y < check.Bottom; y++)
                {
                    int lx = x - CollisionBox.Left;
                    int ly = y - CollisionBox.Top;

                    int o_x = x + (int)otheroffset.X;
                    int o_y = y + (int)otheroffset.Y;

                    int o_lx = o_x - other.CollisionBox.Left;
                    int o_ly = o_y - other.CollisionBox.Top;

                    int d = lx + ly * Width;
                    int o_d = o_lx + o_ly * other.Width;

                    if (thisdata[d].A > 0 && otherdata[o_d].A > 0) //Should be < 1?
                        return true;
                }
            }

            return false;
        }
    }


    public class SpriteSheet
    {
        private Texture2D graphic;

        /// <summary>
        /// Get the specified frame from this spritesheet
        /// </summary>
        /// <param name="frame"></param>
        /// <returns></returns>
        public Texture2D GetFrame(int frame)
        {
            Color[] data = new Color[framesize];
            graphic.GetData<Color>(
                0,
                GetRectangle(frame),
                data,
                0,
                framesize);
            Texture2D ret = new Texture2D(graphic.GraphicsDevice, FrameWidth, FrameHeight);
            ret.SetData<Color>(data);
            return ret;
        }

        /// <summary>
        /// Returns the full spritesheet image
        /// </summary>
        /// <returns></returns>
        public Texture2D GetGraphic()
        {
            return graphic;
        }

        /// <summary>
        /// Get the rectangle on the texture that contains the specified frame
        /// </summary>
        /// <param name="frame"></param>
        /// <returns></returns>
        public Rectangle GetRectangle(int frame)
        {
            int x = frame % framesAcross;
            int y = (int)Math.Floor(frame / (float)framesAcross);
            return new Rectangle(
                x * FrameWidth,
                y * FrameHeight,
                FrameWidth,
                FrameHeight);
        }

        private int framesAcross;
        private int framesDown;

        private int framesize { get { return FrameWidth * FrameHeight; } }

        /// <summary>
        /// The width of one frame from this spritesheet
        /// </summary>
        public int FrameWidth { get { return graphic.Width / framesAcross; } }
        /// <summary>
        /// The height of one frame from this spritesheet
        /// </summary>
        public int FrameHeight { get { return graphic.Height / framesDown; } }

        /// <summary>
        /// The number of frames on this spritesheet
        /// </summary>
        public int FrameCount
        {
            get
            {
                if (lastFrame.HasValue)
                    return (int)lastFrame + 1;
                else
                    return framesAcross * framesDown;
            }
        }

        private int? lastFrame;

        /// <summary>
        /// Set the last frame number on this sheet (use if there are
        /// empty frames at the end of the image used)
        /// </summary>
        /// <param name="frame"></param>
        public void SetLastFrame(int frame)
        {
            lastFrame = frame;
        }

        /// <summary>
        /// Construct a new spritesheet with the given information
        /// </summary>
        /// <param name="graphic">Image containing sprite frames</param>
        /// <param name="device"></param>
        /// <param name="framesAcross"></param>
        /// <param name="framesDown"></param>
        public SpriteSheet(Texture2D graphic, int framesAcross, int framesDown)
        {
            this.graphic = graphic;
            this.framesAcross = framesAcross;
            this.framesDown = framesDown;
        }
    }
}
